﻿using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using FolderSynchronizer;

namespace CustomAction
{
    /// <summary>
    /// Форма продолжения установки.
    /// </summary>
    public partial class FormContinueInstall : Form
    {
        /// <summary>
        /// Синхронизатор файлов.
        /// </summary>
        private Synchronizer sync;

        /// <summary>
        /// Оменена ли задача.
        /// </summary>
        private bool canceled = false;

        /// <summary>
        /// Объект для блокирования одновременного доступа нескольких потоков к секции кода.
        /// </summary>
        private object syncObject = new object();

        /// <summary>
        /// Запущена ли транзакция.
        /// </summary>
        private bool transactionRun = false;

        /// <summary>
        /// Создает форму продолжения установки.
        /// </summary>
        /// <param name="resourcePath">Путь к ресурсу.</param>
        /// <param name="targetPath">Путь к месту установки программы.</param>
        /// <param name="pureFolders">Папки подлежащие удалению.</param>
        /// <param name="excludeFolders">Папки которые нельзя удалять.</param>
        /// <param name="excludeFiles">Файлы которые нельзя удалять.</param>
        /// <param name="fullMatch">Сделать полное соответствие содержания resourcePath и targetPath.</param>
        /// <param name="checkSumFile">Файл с хеш-суммами.</param>
        public FormContinueInstall(string resourcePath, string targetPath, string[] pureFolders, string[] excludeFolders, string[] excludeFiles, bool fullMatch, string checkSumFile = null)
        {
            this.InitializeComponent();

            if (resourcePath == null)
            {
                throw new NullReferenceException("Путь к ресурсу = null.");
            }

            if (targetPath == null)
            {
                throw new NullReferenceException("Путь к месту установки программы = null.");
            }

            this.sync = new Synchronizer(new SynchronizerSettings()
            {
                SourceFolder = resourcePath,
                TargetFolder = targetPath,
                Bar = this.ReportProgress,
                ThrowIfCancellationRequested = this.IsCanceled,
                InTempFolder = this.LockCancelButtonBeforeTransaction,
                PureFolders = pureFolders,
                ExcludeFolders = excludeFolders,
                ExcludeFiles = excludeFiles,
                FullMatchDirectories = fullMatch,
                CheckSumFile = checkSumFile
            });
        }

        /// <summary>
        /// Метод вызывается после открытия формы.
        /// </summary>
        /// <param name="sender">Отправитель.</param>
        /// <param name="e">Событие.</param>
        private void FormContinueInstall_Shown(object sender, EventArgs e)
        {
            var task = new Task(() => this.CopyFiles());

            task.Start();

            task.ContinueWith(this.Completed);
        }

        /// <summary>
        /// Метод завершения задания.
        /// </summary>
        /// <param name="task">Задание.</param>
        private void Completed(Task task)
        {
            if (!this.InvokeRequired)
            {
                if (task.IsFaulted)
                {
                    if (task.Exception.GetBaseException().Message == "Операция отменена.")
                    {
                        this.DialogResult = DialogResult.Cancel;
                    }
                    else
                    {
                        this.DialogResult = DialogResult.Abort;
                    }
                }
                else
                {
                    this.DialogResult = DialogResult.OK;
                }

                this.Hide();
            }
            else
            {
                this.Invoke(new Action<Task>(this.Completed), task);
            }            
        }

        /// <summary>
        /// Метод копирования файлов из папки ресурса в папку установки программы.
        /// </summary>
        private void CopyFiles()
        {
            this.sync.Synchronize();
        }

        /// <summary>
        /// Метод вызываемый для проверки отмены операции.
        /// </summary>
        private void IsCanceled()
        {
            lock (this.syncObject)
            {
                if (this.canceled)
                {
                    throw new OperationCanceledException("Операция отменена.");
                }
            }
        }

        /// <summary>
        /// Выполняет блокирование кнопки отмены, перед запуском транзакции
        /// </summary>
        private void LockCancelButtonBeforeTransaction()
        {
            if (!this.InvokeRequired)
            {
                buttonCancel.Enabled = false;

                this.transactionRun = true;

                this.progressLabel.Text = "Выполнение транзакции";
            }
            else
            {
                this.Invoke(new Action(this.LockCancelButtonBeforeTransaction));
            }
        }

        /// <summary>
        /// Метод для обновления прогресса.
        /// </summary>
        /// <param name="i">Счетчик, какой по счету копируется файл.</param>
        /// <param name="nameFile">Имя файла.</param>
        /// <param name="dirName">Имя директории.</param>
        /// <param name="num">Общее количество файлов для копирования.</param>
        private void ReportProgress(int i, string nameFile, string dirName, int num)
        {
            if (!this.InvokeRequired)
            {
                this.progressBar.Value = Convert.ToInt32(i * 100 / num);

                if (!this.transactionRun)
                {
                    this.progressLabel.Text = "Загрузка файла: " + nameFile;
                }
            }
            else
            {
                this.Invoke(new SynchronizeProgress(this.ReportProgress), new object[] { i, nameFile, dirName, num });
            }
        }

        /// <summary>
        /// Запускается при нажатии кнопки отмены.
        /// </summary>
        /// <param name="sender">Отправитель.</param>
        /// <param name="e">Событие.</param>
        private void ButtonCancel_Click(object sender, EventArgs e)
        {
            buttonCancel.Enabled = false;

            lock (this.syncObject)
            {
                this.canceled = true;
            }
        }
    }
}
